/*-------------------------------------------------------------------------
 * drawElements Quality Program Execution Server
 * ---------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief ExecServer Tests.
 *//*--------------------------------------------------------------------*/

#include "xsDefs.hpp"

#include "xsProtocol.hpp"
#include "deSocket.hpp"
#include "deRingBuffer.hpp"
#include "deFilePath.hpp"
#include "deBlockBuffer.hpp"
#include "deThread.hpp"
#include "deStringUtil.hpp"
#include "deUniquePtr.hpp"

#include "deClock.h"
#include "deProcess.h"
#include "deString.h"
#include "deRandom.h"

#include <memory>
#include <algorithm>

using std::string;
using std::vector;

namespace xs
{

typedef de::UniquePtr<Message> ScopedMsgPtr;

class SocketError : public Error
{
public:
	SocketError (deSocketResult result, const char* message, const char* file, int line)
		: Error		(message, deGetSocketResultName(result), file, line)
		, m_result	(result)
	{
	}

	deSocketResult getResult (void) const
	{
		return m_result;
	}

private:
	deSocketResult m_result;
};

// Helpers.
void sendMessage (de::Socket& socket, const Message& message)
{
	// Format message.
	vector<deUint8> buf;
	message.write(buf);

	// Write to socket.
	size_t pos = 0;
	while (pos < buf.size())
	{
		size_t			numLeft		= buf.size() - pos;
		size_t			numSent		= 0;
		deSocketResult	result		= socket.send(&buf[pos], numLeft, &numSent);

		if (result != DE_SOCKETRESULT_SUCCESS)
			throw SocketError(result, "send() failed", __FILE__, __LINE__);

		pos += numSent;
	}
}

void readBytes (de::Socket& socket, vector<deUint8>& dst, size_t numBytes)
{
	size_t numRead = 0;
	dst.resize(numBytes);
	while (numRead < numBytes)
	{
		size_t			numLeft		= numBytes - numRead;
		size_t			curNumRead	= 0;
		deSocketResult	result		= socket.receive(&dst[numRead], numLeft, &curNumRead);

		if (result != DE_SOCKETRESULT_SUCCESS)
			throw SocketError(result, "receive() failed", __FILE__, __LINE__);

		numRead += curNumRead;
	}
}

Message* readMessage (de::Socket& socket)
{
	// Header.
	vector<deUint8> header;
	readBytes(socket, header, MESSAGE_HEADER_SIZE);

	MessageType	type;
	size_t		messageSize;
	Message::parseHeader(&header[0], (int)header.size(), type, messageSize);

	// Simple messages without any data.
	switch (type)
	{
		case MESSAGETYPE_KEEPALIVE:				return new KeepAliveMessage();
		case MESSAGETYPE_PROCESS_STARTED:		return new ProcessStartedMessage();
		default:
			break; // Read message with data.
	}

	vector<deUint8> messageBuf;
	readBytes(socket, messageBuf, messageSize-MESSAGE_HEADER_SIZE);

	switch (type)
	{
		case MESSAGETYPE_HELLO:					return new HelloMessage(&messageBuf[0], (int)messageBuf.size());
		case MESSAGETYPE_TEST:					return new TestMessage(&messageBuf[0], (int)messageBuf.size());
		case MESSAGETYPE_PROCESS_LOG_DATA:		return new ProcessLogDataMessage(&messageBuf[0], (int)messageBuf.size());
		case MESSAGETYPE_INFO:					return new InfoMessage(&messageBuf[0], (int)messageBuf.size());
		case MESSAGETYPE_PROCESS_LAUNCH_FAILED:	return new ProcessLaunchFailedMessage(&messageBuf[0], (int)messageBuf.size());
		case MESSAGETYPE_PROCESS_FINISHED:		return new ProcessFinishedMessage(&messageBuf[0], (int)messageBuf.size());
		default:
			XS_FAIL("Unknown message");
	}
}

class TestClock
{
public:
	inline TestClock (void)
	{
		reset();
	}

	inline void reset (void)
	{
		m_initTime = deGetMicroseconds();
	}

	inline int getMilliseconds (void)
	{
		return (int)((deGetMicroseconds() - m_initTime) / 1000);
	}

private:
	deUint64 m_initTime;
};

class TestContext
{
public:
						TestContext		(void) : startServer(false) {}

	std::string			serverPath;
	std::string			testerPath;
	de::SocketAddress	address;
	bool				startServer;

	// Passed from execserver.
	std::string			logFileName;
	std::string			caseList;

private:
						TestContext		(const TestContext& other);
	TestContext&		operator=		(const TestContext& other);
};

class TestCase
{
public:
					TestCase		(TestContext& testCtx, const char* name) : m_testCtx(testCtx), m_name(name) {}
	virtual			~TestCase		(void) {}

	const char*		getName			(void) const { return m_name.c_str(); }

	virtual void	runClient		(de::Socket& socket) = DE_NULL;
	virtual void	runProgram		(void) = DE_NULL;

protected:
	TestContext&	m_testCtx;
	std::string		m_name;
};

class TestExecutor
{
public:
					TestExecutor	(TestContext& testCtx);
					~TestExecutor	(void);

	void			runCases		(const std::vector<TestCase*>& testCases);
	bool			runCase			(TestCase* testCase);

private:
	TestContext&	m_testCtx;
};

TestExecutor::TestExecutor (TestContext& testCtx)
	: m_testCtx(testCtx)
{
}

TestExecutor::~TestExecutor (void)
{
}

void TestExecutor::runCases (const std::vector<TestCase*>& testCases)
{
	int numPassed	= 0;
	int numCases	= (int)testCases.size();

	for (std::vector<TestCase*>::const_iterator i = testCases.begin(); i != testCases.end(); i++)
	{
		if (runCase(*i))
			numPassed += 1;
	}

	printf("\n  %d/%d passed!\n", numPassed, numCases);
}

class FilePrinter : public de::Thread
{
public:
	FilePrinter (void)
		: m_curFile(DE_NULL)
	{
	}

	void start (deFile* file)
	{
		DE_ASSERT(!m_curFile);
		m_curFile = file;
		de::Thread::start();
	}

	void run (void)
	{
		char	buf[256];
		deInt64 numRead	= 0;

		while (deFile_read(m_curFile, &buf[0], (deInt64)sizeof(buf), &numRead) == DE_FILERESULT_SUCCESS)
			fwrite(&buf[0], 1, (size_t)numRead, stdout);

		m_curFile = DE_NULL;
	}

private:
	deFile* m_curFile;
};

bool TestExecutor::runCase (TestCase* testCase)
{
	printf("%s\n", testCase->getName());

	bool		success		= false;
	deProcess*	serverProc	= DE_NULL;
	FilePrinter	stdoutPrinter;
	FilePrinter	stderrPrinter;

	try
	{
		if (m_testCtx.startServer)
		{
			string cmdLine = m_testCtx.serverPath + " --port=" + de::toString(m_testCtx.address.getPort());
			serverProc = deProcess_create();
			XS_CHECK(serverProc);

			if (!deProcess_start(serverProc, cmdLine.c_str(), DE_NULL))
			{
				string errMsg = deProcess_getLastError(serverProc);
				deProcess_destroy(serverProc);
				XS_FAIL(errMsg.c_str());
			}

			deSleep(200); /* Give 200ms for server to start. */
			XS_CHECK(deProcess_isRunning(serverProc));

			// Start stdout/stderr printers.
			stdoutPrinter.start(deProcess_getStdOut(serverProc));
			stderrPrinter.start(deProcess_getStdErr(serverProc));
		}

		// Connect.
		de::Socket socket;
		socket.connect(m_testCtx.address);

		// Flags.
		socket.setFlags(DE_SOCKET_CLOSE_ON_EXEC);

		// Run case.
		testCase->runClient(socket);

		// Disconnect.
		if (socket.isConnected())
			socket.shutdown();

		// Kill server.
		if (serverProc && deProcess_isRunning(serverProc))
		{
			XS_CHECK(deProcess_terminate(serverProc));
			deSleep(100);
			XS_CHECK(deProcess_waitForFinish(serverProc));

			stdoutPrinter.join();
			stderrPrinter.join();
		}

		success = true;
	}
	catch (const std::exception& e)
	{
		printf("FAIL: %s\n\n", e.what());
	}

	if (serverProc)
		deProcess_destroy(serverProc);

	return success;
}

class ConnectTest : public TestCase
{
public:
	ConnectTest (TestContext& testCtx)
		: TestCase(testCtx, "connect")
	{
	}

	void runClient (de::Socket& socket)
	{
		DE_UNREF(socket);
	}

	void runProgram (void) { /* nothing */ }
};

class HelloTest : public TestCase
{
public:
	HelloTest (TestContext& testCtx)
		: TestCase(testCtx, "hello")
	{
	}

	void runClient (de::Socket& socket)
	{
		xs::HelloMessage msg;
		sendMessage(socket, (const xs::Message&)msg);
	}

	void runProgram (void) { /* nothing */ }
};

class ExecFailTest : public TestCase
{
public:
	ExecFailTest (TestContext& testCtx)
		: TestCase(testCtx, "exec-fail")
	{
	}

	void runClient (de::Socket& socket)
	{
		xs::ExecuteBinaryMessage execMsg;
		execMsg.name		= "foobar-notfound";
		execMsg.params		= "";
		execMsg.caseList	= "";
		execMsg.workDir		= "";

		sendMessage(socket, execMsg);

		const int		timeout		= 100; // 100ms.
		TestClock		clock;

		for (;;)
		{
			if (clock.getMilliseconds() > timeout)
				XS_FAIL("Didn't receive PROCESS_LAUNCH_FAILED");

			ScopedMsgPtr msg(readMessage(socket));

			if (msg->type == MESSAGETYPE_PROCESS_LAUNCH_FAILED)
				break;
			else if (msg->type == MESSAGETYPE_KEEPALIVE)
				continue;
			else
				XS_FAIL("Invalid message");
		}
	}

	void runProgram (void) { /* nothing */ }
};

class SimpleExecTest : public TestCase
{
public:
	SimpleExecTest (TestContext& testCtx)
		: TestCase(testCtx, "simple-exec")
	{
	}

	void runClient (de::Socket& socket)
	{
		xs::ExecuteBinaryMessage execMsg;
		execMsg.name		= m_testCtx.testerPath;
		execMsg.params		= "--program=simple-exec";
		execMsg.caseList	= "";
		execMsg.workDir		= "";

		sendMessage(socket, execMsg);

		const int		timeout		= 5000; // 5s.
		TestClock		clock;

		bool	gotProcessStarted	= false;
		bool	gotProcessFinished	= false;

		for (;;)
		{
			if (clock.getMilliseconds() > timeout)
				break;

			ScopedMsgPtr msg(readMessage(socket));

			if (msg->type == MESSAGETYPE_PROCESS_STARTED)
				gotProcessStarted = true;
			else if (msg->type == MESSAGETYPE_PROCESS_LAUNCH_FAILED)
				XS_FAIL("Got PROCESS_LAUNCH_FAILED");
			else if (gotProcessStarted && msg->type == MESSAGETYPE_PROCESS_FINISHED)
			{
				gotProcessFinished = true;
				break;
			}
			else if (msg->type == MESSAGETYPE_KEEPALIVE || msg->type == MESSAGETYPE_INFO)
				continue;
			else
				XS_FAIL((string("Invalid message: ") + de::toString(msg->type)).c_str());
		}

		if (!gotProcessStarted)
			XS_FAIL("Did't get PROCESS_STARTED message");

		if (!gotProcessFinished)
			XS_FAIL("Did't get PROCESS_FINISHED message");
	}

	void runProgram (void) { /* print nothing. */ }
};

class InfoTest : public TestCase
{
public:
	std::string infoStr;

	InfoTest (TestContext& testCtx)
		: TestCase	(testCtx, "info")
		, infoStr	("Hello, World")
	{
	}

	void runClient (de::Socket& socket)
	{
		xs::ExecuteBinaryMessage execMsg;
		execMsg.name		= m_testCtx.testerPath;
		execMsg.params		= "--program=info";
		execMsg.caseList	= "";
		execMsg.workDir		= "";

		sendMessage(socket, execMsg);

		const int		timeout		= 10000; // 10s.
		TestClock		clock;

		bool			gotProcessStarted	= false;
		bool			gotProcessFinished	= false;
		std::string		receivedInfo		= "";

		for (;;)
		{
			if (clock.getMilliseconds() > timeout)
				break;

			ScopedMsgPtr msg(readMessage(socket));

			if (msg->type == MESSAGETYPE_PROCESS_STARTED)
				gotProcessStarted = true;
			else if (msg->type == MESSAGETYPE_PROCESS_LAUNCH_FAILED)
				XS_FAIL("Got PROCESS_LAUNCH_FAILED");
			else if (gotProcessStarted && msg->type == MESSAGETYPE_INFO)
				receivedInfo += static_cast<const InfoMessage*>(msg.get())->info;
			else if (gotProcessStarted && msg->type == MESSAGETYPE_PROCESS_FINISHED)
			{
				gotProcessFinished = true;
				break;
			}
			else if (msg->type == MESSAGETYPE_KEEPALIVE)
				continue;
			else
				XS_FAIL("Invalid message");
		}

		if (!gotProcessStarted)
			XS_FAIL("Did't get PROCESS_STARTED message");

		if (!gotProcessFinished)
			XS_FAIL("Did't get PROCESS_FINISHED message");

		if (receivedInfo != infoStr)
			XS_FAIL("Info data doesn't match");
	}

	void runProgram (void) { printf("%s", infoStr.c_str()); }
};

class LogDataTest : public TestCase
{
public:
	LogDataTest (TestContext& testCtx)
		: TestCase(testCtx, "logdata")
	{
	}

	void runClient (de::Socket& socket)
	{
		xs::ExecuteBinaryMessage execMsg;
		execMsg.name		= m_testCtx.testerPath;
		execMsg.params		= "--program=logdata";
		execMsg.caseList	= "";
		execMsg.workDir		= "";

		sendMessage(socket, execMsg);

		const int		timeout		= 10000; // 10s.
		TestClock		clock;

		bool			gotProcessStarted	= false;
		bool			gotProcessFinished	= false;
		std::string		receivedData		= "";

		for (;;)
		{
			if (clock.getMilliseconds() > timeout)
				break;

			ScopedMsgPtr msg(readMessage(socket));

			if (msg->type == MESSAGETYPE_PROCESS_STARTED)
				gotProcessStarted = true;
			else if (msg->type == MESSAGETYPE_PROCESS_LAUNCH_FAILED)
				XS_FAIL("Got PROCESS_LAUNCH_FAILED");
			else if (gotProcessStarted && msg->type == MESSAGETYPE_PROCESS_LOG_DATA)
				receivedData += static_cast<const ProcessLogDataMessage*>(msg.get())->logData;
			else if (gotProcessStarted && msg->type == MESSAGETYPE_PROCESS_FINISHED)
			{
				gotProcessFinished = true;
				break;
			}
			else if (msg->type == MESSAGETYPE_KEEPALIVE)
				continue;
			else if (msg->type == MESSAGETYPE_INFO)
				XS_FAIL(static_cast<const InfoMessage*>(msg.get())->info.c_str());
			else
				XS_FAIL("Invalid message");
		}

		if (!gotProcessStarted)
			XS_FAIL("Did't get PROCESS_STARTED message");

		if (!gotProcessFinished)
			XS_FAIL("Did't get PROCESS_FINISHED message");

		const char* expected = "Foo\nBar\n";
		if (receivedData != expected)
		{
			printf("  received: '%s'\n  expected: '%s'\n", receivedData.c_str(), expected);
			XS_FAIL("Log data doesn't match");
		}
	}

	void runProgram (void)
	{
		deFile* file = deFile_create(m_testCtx.logFileName.c_str(), DE_FILEMODE_OPEN|DE_FILEMODE_CREATE|DE_FILEMODE_TRUNCATE|DE_FILEMODE_WRITE);
		XS_CHECK(file);

		const char line0[] = "Foo\n";
		const char line1[] = "Bar\n";
		deInt64 numWritten = 0;

		// Write first line.
		XS_CHECK(deFile_write(file, line0, sizeof(line0)-1, &numWritten) == DE_FILERESULT_SUCCESS);
		XS_CHECK(numWritten == sizeof(line0)-1);

		// Sleep for 0.5s and write line 2.
		deSleep(500);
		XS_CHECK(deFile_write(file, line1, sizeof(line1)-1, &numWritten) == DE_FILERESULT_SUCCESS);
		XS_CHECK(numWritten == sizeof(line1)-1);

		deFile_destroy(file);
	}
};

class BigLogDataTest : public TestCase
{
public:
	enum
	{
		DATA_SIZE = 100*1024*1024
	};

	BigLogDataTest (TestContext& testCtx)
		: TestCase(testCtx, "biglogdata")
	{
	}

	void runClient (de::Socket& socket)
	{
		xs::ExecuteBinaryMessage execMsg;
		execMsg.name		= m_testCtx.testerPath;
		execMsg.params		= "--program=biglogdata";
		execMsg.caseList	= "";
		execMsg.workDir		= "";

		sendMessage(socket, execMsg);

		const int		timeout		= 30000; // 30s.
		TestClock		clock;

		bool			gotProcessStarted	= false;
		bool			gotProcessFinished	= false;
		int				receivedBytes		= 0;

		for (;;)
		{
			if (clock.getMilliseconds() > timeout)
				break;

			ScopedMsgPtr msg(readMessage(socket));

			if (msg->type == MESSAGETYPE_PROCESS_STARTED)
				gotProcessStarted = true;
			else if (msg->type == MESSAGETYPE_PROCESS_LAUNCH_FAILED)
				XS_FAIL("Got PROCESS_LAUNCH_FAILED");
			else if (gotProcessStarted && msg->type == MESSAGETYPE_PROCESS_LOG_DATA)
				receivedBytes += (int)static_cast<const ProcessLogDataMessage*>(msg.get())->logData.length();
			else if (gotProcessStarted && msg->type == MESSAGETYPE_PROCESS_FINISHED)
			{
				gotProcessFinished = true;
				break;
			}
			else if (msg->type == MESSAGETYPE_KEEPALIVE)
			{
				// Reply with keepalive.
				sendMessage(socket, KeepAliveMessage());
				continue;
			}
			else if (msg->type == MESSAGETYPE_INFO)
				printf("%s", static_cast<const InfoMessage*>(msg.get())->info.c_str());
			else
				XS_FAIL("Invalid message");
		}

		if (!gotProcessStarted)
			XS_FAIL("Did't get PROCESS_STARTED message");

		if (!gotProcessFinished)
			XS_FAIL("Did't get PROCESS_FINISHED message");

		if (receivedBytes != DATA_SIZE)
		{
			printf("  received: %d bytes\n  expected: %d bytes\n", receivedBytes, DATA_SIZE);
			XS_FAIL("Log data size doesn't match");
		}

		int timeMs = clock.getMilliseconds();
		printf("  Streamed %d bytes in %d ms: %.2f MiB/s\n", DATA_SIZE, timeMs, ((float)DATA_SIZE / (float)(1024*1024)) / ((float)timeMs / 1000.0f));
	}

	void runProgram (void)
	{
		deFile* file = deFile_create(m_testCtx.logFileName.c_str(), DE_FILEMODE_OPEN|DE_FILEMODE_CREATE|DE_FILEMODE_TRUNCATE|DE_FILEMODE_WRITE);
		XS_CHECK(file);

		deUint8 tmpBuf[1024*16];
		int numWritten = 0;

		deMemset(&tmpBuf, 'a', sizeof(tmpBuf));

		while (numWritten < DATA_SIZE)
		{
			deInt64 numWrittenInBatch = 0;
			XS_CHECK(deFile_write(file, &tmpBuf[0], de::min((int)sizeof(tmpBuf), DATA_SIZE-numWritten), &numWrittenInBatch) == DE_FILERESULT_SUCCESS);
			numWritten += (int)numWrittenInBatch;
		}

		deFile_destroy(file);
	}
};

class KeepAliveTest : public TestCase
{
public:
	KeepAliveTest (TestContext& testCtx)
		: TestCase(testCtx, "keepalive")
	{
	}

	void runClient (de::Socket& socket)
	{
		// In milliseconds.
		const int	sendInterval			= 5000;
		const int	minReceiveInterval		= 10000;
		const int	testTime				= 30000;
		const int	sleepTime				= 200;
		const int	expectedTimeout			= 40000;
		int			curTime					= 0;
		int			lastSendTime			= 0;
		int			lastReceiveTime			= 0;
		TestClock	clock;

		DE_ASSERT(sendInterval < minReceiveInterval);

		curTime = clock.getMilliseconds();

		while (curTime < testTime)
		{
			bool tryGetKeepalive = false;

			if (curTime-lastSendTime > sendInterval)
			{
				printf("  %d ms: sending keepalive\n", curTime);
				sendMessage(socket, KeepAliveMessage());
				curTime = clock.getMilliseconds();
				lastSendTime = curTime;
				tryGetKeepalive = true;
			}

			if (tryGetKeepalive)
			{
				// Try to acquire keepalive.
				printf("  %d ms: waiting for keepalive\n", curTime);
				ScopedMsgPtr msg(readMessage(socket));
				int recvTime = clock.getMilliseconds();

				if (msg->type != MESSAGETYPE_KEEPALIVE)
					XS_FAIL("Got invalid message");

				printf("  %d ms: got keepalive\n", curTime);

				if (recvTime-lastReceiveTime > minReceiveInterval)
					XS_FAIL("Server doesn't send keepalives");

				lastReceiveTime = recvTime;
			}

			deSleep(sleepTime);
			curTime = clock.getMilliseconds();
		}

		// Verify that server actually kills the connection upon timeout.
		sendMessage(socket, KeepAliveMessage());
		printf("  waiting %d ms for keepalive timeout...\n", expectedTimeout);
		bool isClosed = false;

		try
		{
			// Reset timer.
			clock.reset();
			curTime = clock.getMilliseconds();

			while (curTime < expectedTimeout)
			{
				// Try to get keepalive message.
				ScopedMsgPtr msg(readMessage(socket));
				if (msg->type != MESSAGETYPE_KEEPALIVE)
					XS_FAIL("Got invalid message");

				curTime = clock.getMilliseconds();
				printf("  %d ms: got keepalive\n", curTime);
			}
		}
		catch (const SocketError& e)
		{
			if (e.getResult() == DE_SOCKETRESULT_CONNECTION_CLOSED)
			{
				printf("  %d ms: server closed connection", clock.getMilliseconds());
				isClosed = true;
			}
			else
				throw;
		}

		if (isClosed)
			printf("  ok!\n");
		else
			XS_FAIL("Server didn't close connection");
	}

	void runProgram (void) { /* nothing */ }
};

void printHelp (const char* binName)
{
	printf("%s:\n", binName);
	printf("  --client=[name]       Run test [name]\n");
	printf("  --program=[name]      Run program for test [name]\n");
	printf("  --host=[host]         Connect to host [host]\n");
	printf("  --port=[name]         Use port [port]\n");
	printf("  --tester-cmd=[cmd]    Launch tester with [cmd]\n");
	printf("  --server-cmd=[cmd]    Launch server with [cmd]\n");
	printf("  --start-server        Start server for test execution\n");
}

struct CompareCaseName
{
	std::string name;

	CompareCaseName (const string& name_) : name(name_) {}

	bool operator() (const TestCase* testCase) const
	{
		return name == testCase->getName();
	}
};

void runExecServerTests (int argc, const char* const* argv)
{
	// Construct test context.
	TestContext testCtx;

	testCtx.serverPath	= "execserver";
	testCtx.testerPath	= argv[0];
	testCtx.startServer	= false;
	testCtx.address.setHost("127.0.0.1");
	testCtx.address.setPort(50016);

	std::string runClient = "";
	std::string runProgram = "";

	// Parse command line.
	for (int argNdx = 1; argNdx < argc; argNdx++)
	{
		const char* arg = argv[argNdx];

		if (deStringBeginsWith(arg, "--client="))
			runClient = arg+9;
		else if (deStringBeginsWith(arg, "--program="))
			runProgram = arg+10;
		else if (deStringBeginsWith(arg, "--port="))
			testCtx.address.setPort(atoi(arg+7));
		else if (deStringBeginsWith(arg, "--host="))
			testCtx.address.setHost(arg+7);
		else if (deStringBeginsWith(arg, "--server-cmd="))
			testCtx.serverPath = arg+13;
		else if (deStringBeginsWith(arg, "--tester-cmd="))
			testCtx.testerPath = arg+13;
		else if (deStringBeginsWith(arg, "--deqp-log-filename="))
			testCtx.logFileName = arg+20;
		else if (deStringBeginsWith(arg, "--deqp-caselist="))
			testCtx.caseList = arg+16;
		else if (deStringEqual(arg, "--deqp-stdin-caselist"))
		{
			// \todo [pyry] This is rather brute-force solution...
			char c;
			while (fread(&c, 1, 1, stdin) == 1 && c != 0)
				testCtx.caseList += c;
		}
		else if (deStringEqual(arg, "--start-server"))
			testCtx.startServer = true;
		else
		{
			printHelp(argv[0]);
			return;
		}
	}

	// Test case list.
	std::vector<TestCase*> testCases;
	testCases.push_back(new ConnectTest(testCtx));
	testCases.push_back(new HelloTest(testCtx));
	testCases.push_back(new ExecFailTest(testCtx));
	testCases.push_back(new SimpleExecTest(testCtx));
	testCases.push_back(new InfoTest(testCtx));
	testCases.push_back(new LogDataTest(testCtx));
	testCases.push_back(new KeepAliveTest(testCtx));
	testCases.push_back(new BigLogDataTest(testCtx));

	try
	{
		if (!runClient.empty())
		{
			// Run single case.
			vector<TestCase*>::iterator casePos = std::find_if(testCases.begin(), testCases.end(), CompareCaseName(runClient));
			XS_CHECK(casePos != testCases.end());
			TestExecutor executor(testCtx);
			executor.runCase(*casePos);
		}
		else if (!runProgram.empty())
		{
			// Run program part.
			vector<TestCase*>::iterator casePos = std::find_if(testCases.begin(), testCases.end(), CompareCaseName(runProgram));
			XS_CHECK(casePos != testCases.end());
			(*casePos)->runProgram();
			fflush(stdout);	// Make sure handles are flushed.
			fflush(stderr);
		}
		else
		{
			// Run all tests.
			TestExecutor executor(testCtx);
			executor.runCases(testCases);
		}
	}
	catch (const std::exception& e)
	{
		printf("ERROR: %s\n", e.what());
	}

	// Destroy cases.
	for (std::vector<TestCase*>::const_iterator i = testCases.begin(); i != testCases.end(); i++)
		delete *i;
}

} // xs

#if 0
void testProcFile (void)
{
	/* Test file api. */
	if (deFileExists("test.txt"))
		deDeleteFile("test.txt");
	deFile* file = deFile_create("test.txt", DE_FILEMODE_CREATE|DE_FILEMODE_WRITE);
	const char test[] = "Hello";
	XS_CHECK(deFile_write(file, test, sizeof(test), DE_NULL) == DE_FILERESULT_SUCCESS);
	deFile_destroy(file);

	/* Read. */
	char buf[10] = { 0 };
	file = deFile_create("test.txt", DE_FILEMODE_OPEN|DE_FILEMODE_READ);
	XS_CHECK(deFile_read(file, buf, sizeof(test), DE_NULL) == DE_FILERESULT_SUCCESS);
	printf("buf: %s\n", buf);
	deFile_destroy(file);

	/* Process test. */
	deProcess* proc = deProcess_create("ls -lah /Users/pyry", DE_NULL);
	deFile* out = deProcess_getStdOut(proc);

	deInt64 numRead = 0;
	printf("ls:\n");
	while (deFile_read(out, buf, sizeof(buf)-1, &numRead) == DE_FILERESULT_SUCCESS)
	{
		buf[numRead] = 0;
		printf("%s", buf);
	}
	deProcess_destroy(proc);
}
#endif

#if 0
void testBlockingFile (const char* filename)
{
	deRandom	rnd;
	int			dataSize	= 1024*1024;
	deUint8*	data		= (deUint8*)deCalloc(dataSize);
	deFile*		file;

	deRandom_init(&rnd, 0);

	if (deFileExists(filename))
		DE_VERIFY(deDeleteFile(filename));

	/* Fill in with random data. */
	DE_ASSERT(dataSize % sizeof(int) == 0);
	for (int ndx = 0; ndx < (int)(dataSize/sizeof(int)); ndx++)
		((deUint32*)data)[ndx] = deRandom_getUint32(&rnd);

	/* Write with random-sized blocks. */
	file = deFile_create(filename, DE_FILEMODE_CREATE|DE_FILEMODE_WRITE);
	DE_VERIFY(file);

	int curPos = 0;
	while (curPos < dataSize)
	{
		int				blockSize	= 1 + deRandom_getUint32(&rnd) % (dataSize-curPos);
		deInt64			numWritten	= 0;
		deFileResult	result		= deFile_write(file, &data[curPos], blockSize, &numWritten);

		DE_VERIFY(result == DE_FILERESULT_SUCCESS);
		DE_VERIFY(numWritten == blockSize);

		curPos += blockSize;
	}

	deFile_destroy(file);

	/* Read and verify file. */
	file	= deFile_create(filename, DE_FILEMODE_OPEN|DE_FILEMODE_READ);
	curPos	= 0;
	while (curPos < dataSize)
	{
		deUint8			block[1024];
		int				numToRead	= 1 + deRandom_getUint32(&rnd) % deMin(dataSize-curPos, DE_LENGTH_OF_ARRAY(block));
		deInt64			numRead		= 0;
		deFileResult	result		= deFile_read(file, block, numToRead, &numRead);

		DE_VERIFY(result == DE_FILERESULT_SUCCESS);
		DE_VERIFY((int)numRead == numToRead);
		DE_VERIFY(deMemCmp(block, &data[curPos], numToRead) == 0);

		curPos += numToRead;
	}
	deFile_destroy(file);
}
#endif

int main (int argc, const char* const* argv)
{
	xs::runExecServerTests(argc, argv);
	return 0;
}
